Jerry Greenough

jerry.greenough@jgxsoft.com
www.jgxsoft.com



Processing an STL File

The STL format is a CAD format used widely in 3D printing and rapid prototyping for the representation or approximation of surfaces by means of tesssellation (a collection of triangular facets). In general the triangular facets correspond to descriptions of the surfaces that bound a solid. There are many on-line repositories for STL files as well as files written in other CAD formats:- GrabCAD is just one example.

There are two kinds of STL file, both of which carry the file extension .stl. The ASCII version of STL is mainly used with smaller collections of triangular facets, whereas the binary form is used for higher resolution representation of groups of surfaces. An example of a simple ASCII STL file that represents an icosahedron can be accessed through the link on the right. This iocosahedron is bounded by a surface composed of 20 equilateral triangles of unit length.


Exporting an STL File

The icosahedron provides a useful example for demonstrating how to create an ASCII STL file. A sample of C++ source code that does this can be obtained from the link below. The program not only illustrates the creation of an STL file but also provides the vertex locations for the icosahedron.

Icosahedron creation - Icogen.cpp

The STL format is simply a listing of triangular facets, with each facet defined in terms of (i) its normal and (ii) its three vertices. Not all STL reading software requires an accurate representation of the normal, but a proper normal is calculated Icogen.cpp just in case the intended STL reader does require one. This is an example of a facet written in STL format:

facet normal -0.356822 0.794654 0.491123
outer loop
vertex 0 0 0.850651
vertex -0.809017 0 0.262866
vertex 0 0.525731 0
endloop
endfacet

An example of source code that exports a facet is shown on the right. The function STLFacetOut(...) makes use of a vertex entity to store a location or, if needed, to store a direction. Accordingly the vertex entity has three components of type float, namely x, y and z. An iterator that points to a sequence of three vertex entities that are housed in a const vector<vertex> is given as an input, as well as a normal (stored in a vertex entity for convenience) together with an output fstream associated with the ASCII STL file to which we wish to write.

The STLExport(...) function (see right) exports the STL file in its entirity. It accepts a reference to an output fstream associated with the ASCII STL file along with a reference to a constant vector of 60 vertex entities called vertices. This vector is initialized in a parent function createIcosahedron(...) (see Icogen.cpp for details). Whilst a true icosahedron has only 12 vertices, the STL format does not provide a means to link a facet's vertex to a previous facet's vertex. Each of the 20 facets therefore references 3 unique vertices, making a total of 60 = 20*3 vertices.

The vector of vertices is equipped with an iterator that enables us to identify three 'contiguous' vertices at a time and subsequently present them to the STLFacetOut(...) function. Notice also that the facet normal is calculated here. This is achieved by overloading the caret (^) operator for a function that takes two vertex inputs and produces a vertex return. The result of the cross-product is normalized by the unit() funtion. Please see Icogen.cpp for the implementation details of both the unit() function and overloading the caret operator.


Importing an STL File

This example demonstrates some source code that can be used to import a binary STL file. The format of a binary STL file is discussed here:- Binary STL. A sample of C++ source code that performs the binary STL import can be obtained from the link below.

Import a binary STL file - STLImport.cpp

The top-level function is STLImport which accepts an STL file name as a string and subsequently opens the file using an input fstream object. Thereafter it is algorithmically tempting to program the binary import as a sequence of ifstream read statements. However, the latency that is typicallly inherent in file operations might lead to some lengthy execution times, particularly for STL files with thousands of facets. For this reason, the size of the file is calculated beforehand. Enough memory is then reserved to house all the data in just one read. The data is then parsed from heap memory, which in general has a lower latency. This is illustrated with the code snippet to the right.

Given that the .stl file extension can be used for both ASCII and binary STL files, it is necessary to test if the STL file is in binary format. This is undertaken by the function isBinarySTL(...). The test checks to see if the very first non-space token is "solid" and subsequently verifies whether the first token on the next line is "facet". If the two tokens appear in this way, then the STL file is assumed to be ASCII, otherwise it is assumed to be a binary STL file.


Jerry Greenough Jerry Greenough
icosahedron.stl
void STLFacetOut(ofstream & stlout, vector<vertex>::const_iterator vv, vertex normal) {
    // Export an STL facet to an output stream, stlout.
    stlout << "facet normal " << normal.x << " " << normal.y << " " << normal.z << endl;
    stlout << " outer loop" << endl;
    stlout << " vertex " << vv[0].x << " " << vv[0].y << " " << vv[0].z << endl;
    stlout << " vertex " << vv[1].x << " " << vv[1].y << " " << vv[1].z << endl;
    stlout << " vertex " << vv[2].x << " " << vv[2].y << " " << vv[2].z << endl;
    stlout << " endloop" << endl;
    stlout << "endfacet" << endl;
}
void STLExport(ofstream & stlout, const vector<vertex> & vertices) {
    // Export in STL format to an output stream, stlout.
    vertex normal;

    stlout << "solid STLExport" << endl;

    for (auto q = vertices.begin(); q < vertices.end(); q += 3) {

       // Calculate the facet's unit normal.
       normal = (q[1] - q[0]) ^ (q[2] - q[1]);
       unit(normal);
       STLFacetOut(stlout, q, normal);
    }

    stlout << "endsolid STLExport" << endl;
}
ifstream ifs(fileName, ifstream::binary);

// Get pointer to the associated buffer object.
// rdbuf returns a streambuf object associated with the
// input fstream object ifs.

filebuf* pbuf = ifs.rdbuf();

// Calculate the file's size.

auto size = pbuf->pubseekoff(0, ifs.end);

// Set the position pointer to the beginning of the file.

pbuf->pubseekpos(0);

// Allocate memory to contain file data.

char* buffer = new char[(size_t)size];

// Get file data. sgetn grabs all the characters from the streambuf
// object 'pbuf'. The return value of sgetn is the number of characters
// obtained - ordinarily, this value should be checked for equality
// against the number of characters requested.

pbuf->sgetn(buffer, size);

Copyright © 2018 JGX Software Solutions LLC. All Rights Reserved.